package com.enpronomics.pcall;

import java.util.Iterator;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.location.Criteria;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;

public class MyLocation {

	/**
	 * If GPS is enabled. Use minimal connected satellites count.
	 */
	private static final int min_gps_sat_count = 3;

	/**
	 * Iteration step time.
	 */
	private static final int iteration_timeout_step = 500;

	public static int OPEN_GPS_DELAY = 0 * 1000;

	private LocationResult locationResult;
	private Location bestLocation = null;
	private Handler handler = new Handler();
	private LocationManager myLocationManager;
	public Context context;

	private int counts = 0;
	private int sat_count = 0;

	private boolean gps_enabled = false;
	private boolean network_enabled = false;

	private Runnable showTime = new Runnable() {

		public void run() {
			boolean stop = false;
			counts++;
			System.out.println("counts=" + counts);

			// if timeout (1 min) exceeded, stop tying
			if (counts > 120) {
				stop = true;
			}

			// update last best location
			bestLocation = getLocation(context);

			// if location is not ready or don`t exists, try again
//			if (bestLocation == null) {
//				System.out.println("BestLocation not ready, continue to wait");
//				handler.postDelayed(this, iteration_timeout_step);
//			} else {
				// if best location is known, calculate if we need to continue
				// to look for better location
				// if gps is enabled and min satellites count has not been
				// connected or min check count is smaller then 4 (2 sec)
				if (stop == false && !needToStop()) {
					System.out.println("Connected " + sat_count
							+ " sattelites. continue waiting..");
					handler.postDelayed(this, iteration_timeout_step);
				} else {
					System.out
							.println("#########################################");
					System.out
							.println("BestLocation finded return result to main. sat_count="
									+ sat_count);
					System.out
							.println("#########################################");

					// removing all updates and listeners
					myLocationManager.removeUpdates(gpsLocationListener);
					myLocationManager.removeUpdates(networkLocationListener);
					myLocationManager
							.removeGpsStatusListener(gpsStatusListener);
					sat_count = 0;

					// send best location to locationResult
					locationResult.gotLocation(bestLocation);
				}
			}
//		}
	};

	/**
	 * Determine if continue to try to find best location
	 */
	private Boolean needToStop() {
		if (gps_enabled) {
			if (counts <= 2) {
				return false;
			}
			if (sat_count < min_gps_sat_count) {
				// if 20-25 sec and 3 satellites found then stop
				if (counts >= 40 && sat_count >= 3) {
					return true;
				}
				return false;
			}
		}
		return true;
	}

	/**
	 * Best location abstract result class
	 */
	public static abstract class LocationResult {
		public abstract void gotLocation(Location location);
	}

	/**
	 * Initialize starting values and starting best location listeners
	 * 
	 * @param Context
	 *            ctx
	 * @param LocationResult
	 *            result
	 */
	public boolean init(Context ctx, LocationResult result) {
		context = ctx;
		locationResult = result;

		if (checkConnection()) {

			handler.postDelayed(new Runnable() {
				@Override
				public void run() {
					if (checkConnection()) {
						locationResult.gotLocation(bestLocation);
					} else
						continueInit();
				}
			}, OPEN_GPS_DELAY);
//			context.startActivity(new Intent(context, DialogActivity.class));
			return false;
		}
		return continueInit();
	}

	public boolean checkConnection() {
		if (myLocationManager == null)
			myLocationManager = (LocationManager) context
					.getSystemService(Context.LOCATION_SERVICE);
		try {
			gps_enabled = myLocationManager
					.isProviderEnabled(LocationManager.GPS_PROVIDER);
		} catch (Exception ex) {
		}
		if(!gps_enabled && canToggleGPS()){
			toggleGPS(true);
			try{Thread.sleep(100);}catch (Exception e) {}
			try {
				gps_enabled = myLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
			} catch (Exception ex) {
			}
		}
		try {
			network_enabled = myLocationManager
					.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
		} catch (Exception ex) {
		}
		return (!gps_enabled && !network_enabled);
	}

	public void toggleGPS(boolean enable) {
	    String provider = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);

	    if(provider.contains("gps") == enable) {
	        return; // the GPS is already in the requested state
	    }

	    final Intent poke = new Intent();
	    poke.setClassName("com.android.settings", "com.android.settings.widget.SettingsAppWidgetProvider");
	    poke.addCategory(Intent.CATEGORY_ALTERNATIVE);
	    poke.setData(Uri.parse("3"));
	    context.sendBroadcast(poke);
	}
	public boolean canToggleGPS() {
	    PackageManager pacman = context.getPackageManager();
	    PackageInfo pacInfo = null;

	    try {
	        pacInfo = pacman.getPackageInfo("com.android.settings", PackageManager.GET_RECEIVERS);
	    } catch (NameNotFoundException e) {
	        return false; //package not found
	    }

	    if(pacInfo != null){
	        for(ActivityInfo actInfo : pacInfo.receivers){
	            //test if recevier is exported. if so, we can toggle GPS.
	            if(actInfo.name.equals("com.android.settings.widget.SettingsAppWidgetProvider") && actInfo.exported){
	                return true;
	            }
	        }
	    }
	    
	    return false; //default
	}
	
	private boolean continueInit() {
		if (gps_enabled) {
			myLocationManager.requestLocationUpdates(
					LocationManager.GPS_PROVIDER, 0, 0, gpsLocationListener);
		}
		if (network_enabled) {
			myLocationManager.requestLocationUpdates(
					LocationManager.NETWORK_PROVIDER, 0, 0,
					networkLocationListener);
		}

		bestLocation = null;
		counts = 0;

		// turning on location updates
		myLocationManager.addGpsStatusListener(gpsStatusListener);

		// starting best location finder loop
		handler.postDelayed(showTime, iteration_timeout_step);
		return true;
	}

	/**
	 * GpsStatus listener. OnChainged counts connected satellites count.
	 */
	public final GpsStatus.Listener gpsStatusListener = new GpsStatus.Listener() {
		public void onGpsStatusChanged(int event) {

			if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) {
				try {
					// Check number of satellites in list to determine fix state
					GpsStatus status = myLocationManager.getGpsStatus(null);
					Iterable<GpsSatellite> satellites = status.getSatellites();

					sat_count = 0;

					Iterator<GpsSatellite> satI = satellites.iterator();
					while (satI.hasNext()) {
						GpsSatellite satellite = satI.next();
						System.out.println("Satellite: snr="
								+ satellite.getSnr() + ", elevation="
								+ satellite.getElevation());
						sat_count++;
					}
				} catch (Exception e) {
					e.printStackTrace();
					sat_count = min_gps_sat_count + 1;
				}

				System.out.println("#### sat_count = " + sat_count);
			}
		}
	};

	/**
	 * Gps location listener.
	 */
	public final LocationListener gpsLocationListener = new LocationListener() {
		@Override
		public void onLocationChanged(Location location) {
		}

		public void onProviderDisabled(String provider) {
		}

		public void onProviderEnabled(String provider) {
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
	};

	/**
	 * Network location listener.
	 */
	public final LocationListener networkLocationListener = new LocationListener() {
		@Override
		public void onLocationChanged(Location location) {
		}

		public void onProviderDisabled(String provider) {
		}

		public void onProviderEnabled(String provider) {
		}

		public void onStatusChanged(String provider, int status, Bundle extras) {
		}
	};

	/**
	 * Returns best location using LocationManager.getBestProvider()
	 * 
	 * @param context
	 * @return Location|null
	 */
	public static Location getLocation(Context context) {
		System.out.println("getLocation()");

		// fetch last known location and update it
		try {
			LocationManager lm = (LocationManager) context
					.getSystemService(Context.LOCATION_SERVICE);

			Criteria criteria = new Criteria();
			criteria.setAccuracy(Criteria.ACCURACY_FINE);
			criteria.setAltitudeRequired(false);
			criteria.setBearingRequired(false);
			criteria.setCostAllowed(true);
			String strLocationProvider = lm.getBestProvider(criteria, true);

			System.out.println("strLocationProvider=" + strLocationProvider);
			Location location = lm.getLastKnownLocation(strLocationProvider);
			if (location != null) {
				return location;
			}
			return null;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	// Getting better location [Not used]
	private static final int TWO_MINUTES = 1000 * 60 * 2;

	/**
	 * Determines whether one Location reading is better than the current
	 * Location fix
	 * 
	 * @param location
	 *            The new Location that you want to evaluate
	 * @param currentBestLocation
	 *            The current Location fix, to which you want to compare the new
	 *            one
	 */
	protected boolean isBetterLocation(Location location,
			Location currentBestLocation) {
		if (currentBestLocation == null) {
			// A new location is always better than no location
			return true;
		}

		// Check whether the new location fix is newer or older
		long timeDelta = location.getTime() - currentBestLocation.getTime();
		boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
		boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
		boolean isNewer = timeDelta > 0;

		// If it's been more than two minutes since the current location, use
		// the new location
		// because the user has likely moved
		if (isSignificantlyNewer) {
			return true;
			// If the new location is more than two minutes older, it must be
			// worse
		} else if (isSignificantlyOlder) {
			return false;
		}

		// Check whether the new location fix is more or less accurate
		int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation
				.getAccuracy());
		boolean isLessAccurate = accuracyDelta > 0;
		boolean isMoreAccurate = accuracyDelta < 0;
		boolean isSignificantlyLessAccurate = accuracyDelta > 200;

		// Check if the old and new location are from the same provider
		boolean isFromSameProvider = isSameProvider(location.getProvider(),
				currentBestLocation.getProvider());

		// Determine location quality using a combination of timeliness and
		// accuracy
		if (isMoreAccurate) {
			return true;
		} else if (isNewer && !isLessAccurate) {
			return true;
		} else if (isNewer && !isSignificantlyLessAccurate
				&& isFromSameProvider) {
			return true;
		}
		return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
		if (provider1 == null) {
			return provider2 == null;
		}
		return provider1.equals(provider2);
	}
}